
/**
 * @author Ednei Leite da Silva
 * @since 01/04/2014
 * @version 1.0
 */

#include "servidor.h"
#include "sniffer.h"

/*
 * Função que indica se a mensagem chegou completamente
 * mensagem Mensagem recebida
 * Retorna 1 caso esteja completa caso contrario retorna 0.
 */
int fecha_tag_mensagem(char *mensagem) {
    return (strncmp(mensagem + (strlen(mensagem) - STR_F_MENSAGEM_LEN), STR_F_MENSAGEM, STR_F_MENSAGEM_LEN) == 0 ? 1 : 0);
}

/*
 * Função que retira o conteudo da mensagem
 * destino Ponteiro que recebe o conteudo da mensagem
 * origem Ponteiro com a mensagem
 */
void conteudo_mensagem(char *destino, char *origem) {
    int len;

    len = strlen(origem);

    strncpy(destino, origem + STR_I_MENSAGEM_LEN, (len - (STR_F_MENSAGEM_LEN + STR_I_MENSAGEM_LEN)));
}

/*
 * Função que obtem o conteudo inserido dentro da tags
 * retorno Contem os conteudo estraido da mensagem
 * str Mensagem passada em formato xml
 * elementos quantidades de conteudo dentro mensagem
 */
void obtem_conteudo_tag(char **retorno, const char *str, int elementos) {
    int matriz = 0, vetor = 0, cont = 0;

    while (str[cont] && elementos) {

        /* retira as tags */
        if (str[cont] == '<') {
            while (str[cont] && str[cont++] != '>');
        } else {
            vetor = 0;

            /*
             * Retira os conteudo e a
             * armazena em retorno
             */
            while (str[cont] && str[cont] != '<') {
                retorno[matriz][vetor] = str[cont];
                vetor++;
                cont++;
            }

            retorno[matriz][vetor] = '\0';

            if (!elementos--) break;

            matriz++;
        }
    }
}

/*
 * Função que retorna qual tipo de operação a ser realizada
 * mensagem String com uma operação
 * Retorna 1 para captura uma interface, 2 Captura de pacotes,
 * 3 realizar login, 4 executar um comando no iptables, 5 upload de regras
 * . Caso não corresponda a nenhuma retorna 0.
 */
int operacao(char *mensagem) {
    int retorno = 0;

    if (strncmp(mensagem, STR_INTERFACE, STR_INTERFACE_LEN) == 0) {
        retorno = 1;
    } else if (strncmp(mensagem, STR_CAPTURA, STR_CAPTURA_LEN) == 0) {
        retorno = 2;
    } else if (strncmp(mensagem, STR_LOGIN, STR_LOGIN_LEN) == 0) {
        retorno = 3;
    } else if (strncmp(mensagem, STR_COMANDO, STR_COMANDO_LEN) == 0) {
        retorno = 4;
    } else if (strncmp(mensagem, STR_UPLOAD, STR_UPLOAD_LEN) == 0) {
        retorno = 5;
    } else if (strncmp(mensagem, STR_FINAL, STR_FINAL_LEN) == 0) {
        retorno = 6;
    }

    return retorno;
}

/*
 * Função abstrai processo de captura de pacotes
 * str String contendo interfaces de rede e especificação da filtragem (regra).
 * socket Descritor do socket
 */
void realiza_captura(char *str, int socket) {
    char **captura, *msg;
    pcap_t* descr;

    /*
     * formato da string recebida com os
     * dados a para realizar captura
     * <interface>eth0</interface><filtro>tcp port 80</filtro><qtpacotes>100</qtpacotes>
     */
    captura = (char**) malloc(sizeof (char) * 3);
    captura[0] = (char*) malloc(sizeof (char) * 100);
    captura[1] = (char*) malloc(sizeof (char) * 200);
    captura[2] = (char*) malloc(sizeof (char) * 10);

    msg = (char*) calloc(sizeof (char), 100);

    obtem_conteudo_tag(captura, str, 3);

    /* Adiciona uma opção a regra evitando captura de pacotes direcionados
     * a porta do servidor
     */
    sprintf(captura[1], "%s %s (port ! %d) and (port ! %d)",
            (strncmp(captura[1], "vazio", 5) == 0 ? " " : captura[1]),
            (strncmp(captura[1], "vazio", 5) == 0 ? " " : "and"),
            PORTA_SERVIDOR,
            PORTA_SSH);

    /* Realiza captura de pacotes */
    if ((descr = inicializaCapturaPacotes(captura[0], captura[1], msg))) {
        capture_loop(descr, atoi(captura[2]), socket);
    } else {
        send(socket, msg, BUFFER_SERVIDOR, MSG_WAITALL);
    }

    sprintf(msg, "<mensagem><fim_captura></mensagem>");
    send(socket, msg, BUFFER_SERVIDOR, MSG_WAITALL);

    free(captura[0]);
    free(captura[1]);
    free(captura[2]);
    free(captura);
}

/*
 * Função que abre conexão com o banco de dados
 * file Nome do arquivo .db do banco sqlite3
 * Retorna uma conexão com o banco de dados
 */
sqlite3 *connectDB(const char *file) {
    sqlite3 *db;
    if (sqlite3_open(file, &db) != SQLITE_OK) {
        db = NULL;
    }
    return db;
}

/*
 * Função que executa uma query.
 * sql Query a ser executada.
 * db Conexão para o banco de dados.
 * Retorna instancia da execução da operação
 */
sqlite3_stmt *query(const char *sql, sqlite3 *db) {
    sqlite3_stmt *statement;

    /* Realiza execução da query e instanciamento da variavel statement */
    if (sqlite3_prepare_v2(db, sql, -1, &statement, 0) != SQLITE_OK) return NULL;

    /* Caso seja uma operação de select retorna instancia antes de finalizar */
    if (sqlite3_column_count(statement)) {
        return statement;
    }

    sqlite3_step(statement);

    /* Finaliza instanciamento caso seja update ou insert */
    sqlite3_finalize(statement);

    return statement;
}

/*
 * Método que executa função de insert no banco de dados
 * e retorna 1 em caso de Sucesso e 0 de erro
 */
int insert_sql(const char *sql, sqlite3 *db) {
    sqlite3_stmt *statement;

    if (sqlite3_prepare_v2(db, sql, -1, &statement, 0) != SQLITE_OK) return 0;

    return sqlite3_step(statement);
}

/*
 * Função que realiza busca de usuário autorizados no servidor.
 * str String com os dados em formato XML com os dados Usuário e Senha
 * Retorna 1 caso usuário exista, 0 não encontre.
 */
int efetua_login(char *str) {
    char **acesso, *sql, *usuario, *senha;
    int len;
    sqlite3 *db;
    sqlite3_stmt *stmt;

    acesso = (char**) malloc(sizeof (char) * 2);
    usuario = (char*) malloc(sizeof (char) * 50);
    senha = (char*) malloc(sizeof (char) * 50);

    acesso[0] = usuario;
    acesso[1] = senha;

    sql = (char*) malloc(sizeof (char) * 250);

    /* extrai usuario e senha */
    obtem_conteudo_tag(acesso, str, 2);

    /*
     * sql para verificar se usuário e senha estão corretos
     * criptografa senha antes de pesquisas
     */
    sprintf(sql, "SELECT EXISTS(SELECT * FROM acesso WHERE usuario = \'%s\' AND senha = \'%s\')", usuario, crypt(senha, SALT));

    db = connectDB(FILE_BD);

    stmt = query(sql, db);

    if (sqlite3_column_count(stmt) > 0) {

        /*
         * verifica se a consulta retornou algum dados
         */
        if (SQLITE_ROW == sqlite3_step(stmt)) {
            /* caso usuário e senha estejam corretos retorna 1 caso contrario 0 */
            len = (atoi((const char*) sqlite3_column_text(stmt, 0)) == 1 ? 1 : 0);
        } else {
            printf("Erro ao executar login\n");
            len = 0;
        }
    }

    sqlite3_finalize(stmt);

    sqlite3_close(db);

    free(usuario);
    free(senha);
    free(acesso);

    free(sql);

    return len;
}

/*
 * Função que executa a inserção de regras vinda do cliente
 * e chama gera_script para criar arquivo com as regras
 */
void executa_comando(int socket) {
    sqlite3 *db;
    char *msg;
    int flag = 1;

    msg = (char*) malloc(sizeof (char) * BUFFER_SERVIDOR + 1);

    db = connectDB(FILE_BD);

    /*
     * Inicia transaction pois se ocorrer erro durante a execução
     * é possível reverter as operações anteriores
     */
    sprintf(msg, "BEGIN;");
    query(msg, db);

    /* remove todas a regras anteriores */
    sprintf(msg, "DELETE FROM regras_firewall");
    query(msg, db);

    do {
        recv(socket, msg, BUFFER_SERVIDOR, MSG_WAITALL);

        if (strcmp(msg, "<mensagem><fim_sql></comando>")) {

            /*
             * insere as regras recebidas do cliente
             * caso ocorra algum erro encerra loop
             * e cancela alterações (ROLLBACK)
             */

            printf("%s\n", msg);

            if (insert_sql(msg, db) != SQLITE_DONE) {
                sprintf(msg, "ROLLBACK;");
                query(msg, db);
                flag = 0;
                break;
            }
        } else {
            break;
        }

        memset(msg, '\0', sizeof (char));

    } while (1);

    /* finaliza gravando alterações */
    sprintf(msg, "COMMIT;");
    query(msg, db);

    sqlite3_close(db);

    free(msg);

    /* caso não ocorra nenhum erro gera script de configuração */
    if (flag == 1) {
        gera_script();
    }
}

/*
 * Função que cria regras de configuração do firewall e
 * escreve em arquivo para ser executado.
 */
void gera_script(void) {
    const char *regra, *dados_regra, *tabela;
    int flag, cont = 0;
    sqlite3 *db;
    sqlite3_stmt *stmt_regra;
    FILE *arq;

    /* criar arquivo que ira armazenar regras do firewall */
    arq = fopen(FILE_REGRAS, "w");

    if (arq != NULL) {

        /*
         * Através de operações de concatenação e unions
         * e gerada as regras de firewall e as políticas padrão.
         * Somente é gerada política padrão para as tabelas que possuem
         * regras de firewall criadas
         */
        regra = "SELECT LOWER(t_nome) AS t_nome, "
                "'C' AS parte, c_nome, 0 AS rf_linha, "
                "':' || c_nome || ' ' || ta_nome || ' [0:0]' AS regras "
                "FROM chains_tabela "
                "INNER JOIN chains ON chains_tabela.ct_chain = chains.c_id "
                "INNER JOIN tabelas ON chains_tabela.ct_tabela = tabelas.t_id "
                "INNER JOIN target ON chains_tabela.ct_politica = target.ta_id "
                "WHERE t_nome IN (SELECT t_nome FROM regras_firewall "
                "INNER JOIN chains_tabela ON regras_firewall.rf_chain_tabela = chains_tabela.ct_id "
                "INNER JOIN tabelas ON chains_tabela.ct_tabela = tabelas.t_id "
                "GROUP BY t_nome) "
                "UNION "
                "SELECT LOWER(t_nome) AS t_nome, "
                "'R' AS parte, c_nome, rf_linha, "
                "'-A ' || c_nome || "
                "CASE WHEN rf_not_endereco_origem = 1 AND rf_endereco_origem NOT NULL THEN ' ! -s ' || rf_endereco_origem "
                "WHEN rf_endereco_origem NOT NULL THEN ' -s ' || rf_endereco_origem "
                "ELSE '' END || "
                "CASE WHEN rf_not_endereco_destino = 1 AND rf_endereco_destino NOT NULL THEN ' ! -d ' || rf_endereco_destino "
                "WHEN rf_endereco_destino NOT NULL THEN ' -d ' || rf_endereco_destino "
                "ELSE '' END || "
                "CASE WHEN rf_modulo IS NULL THEN '' "
                "WHEN om_string = 1 THEN ' -m ' || m_nome || ' ' || om_nome || ' \"' || rf_parametros_modulo || '\"' "
                "ELSE ' -m ' || m_nome || ' ' || om_nome || ' ' || rf_parametros_modulo END || "
                "CASE WHEN rf_protocolo IS NULL THEN '' "
                "WHEN rf_argumento_protocolo IS NULL AND rf_not_protocolo = '1' AND rf_porta_origem IS NULL AND rf_porta_destino IS NULL THEN "
                "' ! -p ' || p_nome "
                "WHEN rf_argumento_protocolo IS NULL AND rf_not_protocolo = '1' AND rf_porta_origem NOT NULL AND rf_porta_destino NOT NULL THEN "
                "' -p ' || p_nome "
                "WHEN rf_argumento_protocolo IS NULL AND rf_not_protocolo = '0' THEN  ' -p ' || p_nome "
                "WHEN rf_parametros_protocolo IS NULL AND rf_not_protocolo = '1' THEN ' -p ' || p_nome || ' ! ' || op_nome "
                "WHEN rf_parametros_protocolo IS NULL AND rf_not_protocolo = '0' THEN ' -p ' || p_nome || ' ' || op_nome "
                "WHEN rf_not_protocolo = '1' AND op_string = 1 THEN ' -p ' || p_nome || ' ! ' || op_nome || ' \"' || rf_parametros_protocolo || '\"' "
                "WHEN rf_not_protocolo = '1' THEN ' -p ' || p_nome || ' ! ' || op_nome || ' ' || rf_parametros_protocolo "
                "WHEN op_string = 1 THEN ' -p ' || p_nome || ' ' || op_nome || ' \"' || rf_parametros_protocolo || '\"' "
                "ELSE ' -p ' || p_nome || ' ' || op_nome || ' ' || rf_parametros_protocolo END || "
                "CASE WHEN rf_protocolo IS NULL OR rf_porta_origem IS NULL THEN '' "
                "WHEN rf_not_porta_origem = '1' THEN ' ! --sport ' || rf_porta_origem "
                "ELSE ' --sport ' || rf_porta_origem END || "
                "CASE WHEN rf_protocolo IS NULL OR rf_porta_destino IS NULL THEN '' "
                "WHEN rf_not_porta_destino = '1' THEN ' ! --dport ' || rf_porta_destino "
                "ELSE ' --dport ' || rf_porta_destino END || "
                "CASE WHEN rf_not_interface_rede = 1 AND rf_interface_rede NOT NULL THEN "
                "' ! -' || rf_interface_rede_saida_entrada || ' ' || rf_interface_rede "
                "WHEN rf_interface_rede NOT NULL THEN "
                "' -' || rf_interface_rede_saida_entrada || ' ' || rf_interface_rede "
                "ELSE '' END || "
                "CASE WHEN ot_nome IS NULL THEN ' -j ' || ta_nome "
                "WHEN ot_nome NOT NULL AND rf_parametros_opcao IS NULL THEN ' -j ' || ta_nome || ' ' || ot_nome "
                "WHEN ot_nome NOT NULL AND ot_string = 1 THEN ' -j ' || ta_nome || ' ' || ot_nome || ' \"' || rf_parametros_opcao || '\"' "
                "ELSE ' -j ' || ta_nome || ' ' || ot_nome || ' ' || rf_parametros_opcao END AS regras "
                "FROM regras_firewall "
                "INNER JOIN chains_tabela ON regras_firewall.rf_chain_tabela = chains_tabela.ct_id "
                "INNER JOIN tabelas ON chains_tabela.ct_tabela = tabelas.t_id "
                "INNER JOIN chains ON chains_tabela.ct_chain = chains.c_id "
                "INNER JOIN target ON regras_firewall.rf_target = target.ta_id "
                "LEFT JOIN opcoes_target ON target.ta_id = opcoes_target.ot_target "
                "AND opcoes_target.ot_id = regras_firewall.rf_opcao_target "
                "LEFT JOIN opcoes_modulos ON regras_firewall.rf_modulo = opcoes_modulos.om_id "
                "AND regras_firewall.rf_modulo = opcoes_modulos.om_id "
                "LEFT JOIN modulos ON opcoes_modulos.om_modulo = modulos.m_id "
                "LEFT JOIN protocolos ON regras_firewall.rf_protocolo = protocolos.p_id "
                "LEFT JOIN opcoes_protocolos ON regras_firewall.rf_argumento_protocolo = opcoes_protocolos.op_id "
                "AND protocolos.p_id = opcoes_protocolos.op_protocolo "
                "ORDER BY t_nome, parte, c_nome, rf_linha";


        db = connectDB(FILE_BD);

        stmt_regra = query(regra, db);

        flag = 0;

        while (SQLITE_ROW == sqlite3_step(stmt_regra)) {

            /* Se tabela igual a 0 identifica que iniciou uma nova tabela */
            dados_regra = (const char*) sqlite3_column_text(stmt_regra, 0);
            tabela = (const char*) sqlite3_column_text(stmt_regra, 3);

            /*
             * Se o valor da tabela for igual a 0
             * e so o valor da flag for igual a 0
             * indica que começou uma nova tabela
             * se for a primeira tabela escreve apenas o nome da tabela
             * senão escreve o nome da tabela e COMMIT
             */
            if ((atoi(tabela) == 0) && flag == 0) {
                fprintf(arq, "%s*%s\n", (cont++ ? "COMMIT\n" : ""), dados_regra);
                flag = 1;
            }

            if ((atoi(tabela) != 0)) {
                flag = 0;
            }

            dados_regra = (const char*) sqlite3_column_text(stmt_regra, 4);
            fprintf(arq, "%s\n", dados_regra);
        }

        fprintf(arq, "COMMIT\n");

        sqlite3_finalize(stmt_regra);

        sqlite3_close(db);

    } else {
        printf("Erro ao escrever no arquivo\n\n");
    }

    fclose(arq);

    /*
     * Após fechar o arquivo firewall e feita a
     * operação de restauração das novas regras
     */
    printf("Restore: %d\n", system(CMD_RESTORE_REGRAS));
}

/*
 * Função que envia regras para cliente
 */
void upload_regras(int socket) {
    const char *sql, *dados;
    sqlite3 *db;
    sqlite3_stmt *stmt;

    /*
     * Atraves deste select e gerada a os insert's para fazer upload ao cliente
     * gera tambem os update's das políticas padrão
     */
    sql = "SELECT  'INSERT INTO regras_firewall VALUES(' || rf_id || ', ' || rf_linha || ', ' || rf_chain_tabela || ', ' || "
            "rf_target || ', ' || CASE WHEN rf_opcao_target IS NULL THEN 'NULL' ELSE rf_opcao_target END || ', ' || "
            "CASE WHEN rf_parametros_opcao IS NULL THEN 'NULL' ELSE '\"' || rf_parametros_opcao || '\"' END || ', ' || "
            "CASE WHEN rf_endereco_origem IS NULL THEN 'NULL' ELSE '\"' || rf_endereco_origem || '\"' END || ', ' || "
            "rf_not_endereco_origem || ', ' || "
            "CASE WHEN rf_porta_origem IS NULL THEN 'NULL' ELSE '\"' || rf_porta_origem || '\"' END || ', ' || "
            "rf_not_porta_origem || ', ' || "
            "CASE WHEN rf_endereco_destino IS NULL THEN 'NULL' ELSE '\"' || rf_endereco_destino || '\"' END || ', ' || "
            "rf_not_endereco_destino || ', ' || "
            "CASE WHEN rf_porta_destino IS NULL THEN 'NULL' ELSE '\"' || rf_porta_destino || '\"' END || ', ' || "
            "rf_not_porta_destino || ', ' || "
            "CASE WHEN rf_interface_rede IS NULL THEN 'NULL' ELSE '\"' || rf_interface_rede || '\"' END || ', ' || "
            "CASE WHEN rf_interface_rede_saida_entrada IS NULL THEN 'NULL' ELSE '\"' || rf_interface_rede_saida_entrada || '\"' END || ', ' || "
            "rf_not_interface_rede || ', ' || "
            "CASE WHEN rf_modulo IS NULL THEN 'NULL' ELSE rf_modulo END || ', ' || "
            "CASE WHEN rf_parametros_modulo IS NULL THEN 'NULL' ELSE '\"' || rf_parametros_modulo || '\"' END || ', ' || "
            "CASE WHEN rf_protocolo IS NULL THEN 'NULL' ELSE rf_protocolo END || ', ' || "
            "CASE WHEN rf_argumento_protocolo IS NULL THEN 'NULL' ELSE rf_argumento_protocolo END || ', ' || "
            "CASE WHEN rf_parametros_protocolo IS NULL THEN 'NULL' ELSE '\"' || rf_parametros_protocolo || '\"' END || ', ' || "
            "rf_not_protocolo || ')' "
            "FROM regras_firewall "
            "UNION "
            "SELECT 'UPDATE chains_tabela SET ct_politica = ' || ct_politica  || ' WHERE ct_id = ' || ct_id FROM chains_tabela ";

    db = connectDB(FILE_BD);

    stmt = query(sql, db);

    while (SQLITE_ROW == sqlite3_step(stmt)) {

        /* extrai os insert's e update's gerados */
        dados = (const char*) sqlite3_column_text(stmt, 0);

        /* envia ao cliente */
        send(socket, dados, BUFFER_SERVIDOR, MSG_WAITALL);
    }

    send(socket, "<mensagem><fim_upload></mensagem>", BUFFER_SERVIDOR, MSG_WAITALL);

    sqlite3_finalize(stmt);

    sqlite3_close(db);
}

